﻿namespace EasyToolkit.BindingTemplates.Context
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using EasyToolkit.BindingTemplates.Definitions;
    using EasyToolkit.BindingTemplates.Definitions.Binding;
    using EasyToolkit.BindingTemplates.SortAndFilter;

    public class BindingContext : IBindingContext
    {
        public ITemplateDefinition TemplateDefinition
        { get; protected set; }

        public IBindingContextElement Parent
        { get; private set; }

        public int Occurrences
        { get; private set;}

        public int Index
        { get; private set; }

        public object DataSource
        { get; private set; }

        public ISortAndFilter SortAndFilter
        { get; protected set; }

        public IEnumerable<IBindingContextElement> Elements
        { get; private set; }

        public List<IBindingContextElement> ElementsToRender
        { get; private set; }

        public IBindingContext Header
        { get; private set; }

        public IBindingContext Footer
        { get; private set; }

        #region .ctors
        private BindingContext(IBindingContextElement parent, ITemplateDefinition templateDefinition, object dataSource, int index)
        {
            if (templateDefinition == null)
                throw new ArgumentNullException("'BindingContext' creation failed: the parameter 'templateDefinition' cannot be null");
            TemplateDefinition = templateDefinition;

            Parent = parent;
            DataSource = dataSource;
            Index = index;
        }

        public BindingContext(IBindingContextElement parent, ITemplateDefinition templateDefinition, object dataSource, Dictionary<ITemplateDefinition, ISortAndFilter> sortsAndFilters, int index)
                             : this(parent, templateDefinition, dataSource, index)
        {
            try
            {
                if (templateDefinition.Header != null)
                    Header = BindingContext.CreateHeaderOrFooterInstance(parent, templateDefinition.Header, dataSource);

                if (templateDefinition.Footer != null)
                    Footer = BindingContext.CreateHeaderOrFooterInstance(parent, templateDefinition.Footer, dataSource);

                if (DataSource != null)
                {
                    IEnumerable<object> dataSourceAsList;
                    IBindingDefinition dataSourceType;
                    if (DataSource is IEnumerable)
                    {
                        dataSourceAsList = (DataSource as IEnumerable).Cast<object>().ToList();
                        dataSourceType = BindingDefinitionRoot.CreateInstance(dataSourceAsList.GetType());
                    }
                    else
                    {
                        dataSourceAsList = new object[] { DataSource };
                        dataSourceType = BindingDefinitionRoot.CreateInstance(DataSource.GetType());
                    }

                    if (TemplateDefinition.MainBindingDefinition != null)
                        CheckType(TemplateDefinition.MainBindingDefinition, dataSourceType);

                    if (sortsAndFilters != null)
                    {
                        ISortAndFilter sortAndFilter;
                        if (sortsAndFilters.TryGetValue(TemplateDefinition, out sortAndFilter))
                            SortAndFilter = sortAndFilter;
                    }
                    Occurrences = dataSourceAsList.Count();

                    int elementIndex = 0;
                    Elements = dataSourceAsList.Select(ds => new BindingContextElement(this, ds, sortsAndFilters, elementIndex++)).ToList();
                    if (SortAndFilter != null)
                    {
                        Dictionary<object, IBindingContextElement> contextItemByElement = Elements.ToDictionary(e => e.Element, e => e);
                        IEnumerable<object> elements = SortAndFilter.ExecuteSortAndFilter(contextItemByElement.Keys) as IEnumerable<object>;
                        ElementsToRender = elements.Select(e => { IBindingContextElement el = null;
                                                                  contextItemByElement.TryGetValue(e, out el);
                                                                  return el;})
                                                   .ToList();
                    }
                    else
                        ElementsToRender = Elements.ToList();
                } 
            }
            catch (Exception ex)
            {
                string message = string.Format("Create the 'BindingContext' for template '{0}' failed . {1}", templateDefinition.Name, ex.Message);
                throw new EasyToolkitException(message);
            }
        }

        static private BindingContext CreateHeaderOrFooterInstance(IBindingContextElement parent, ITemplateDefinition templateDefinition, object dataSource)
        {
            BindingContext ret = new BindingContext(parent, templateDefinition, dataSource, 1);
            if (ret.DataSource != null)
            {
                IBindingDefinition dataSourceType = BindingDefinitionRoot.CreateInstance(ret.DataSource.GetType());
                if (ret.TemplateDefinition.MainBindingDefinition != null)
                    ret.CheckType(ret.TemplateDefinition.MainBindingDefinition, dataSourceType);

                ret.Occurrences = 1;
                ret.Elements = new BindingContextElement[] { new BindingContextElement(ret, ret.DataSource, null, 0) };
                ret.ElementsToRender = ret.Elements.ToList();
            }
            return ret;
        }
        #endregion

        #region private methods
        private void CheckType(IBindingDefinition mainBindingDef, IBindingDefinition dataSourceType)
        {
            bool checkTypesOk = false;
            if (dataSourceType.IsACollection)
                checkTypesOk = true;
                //checkTypesOk = mainBindingDef.BindingType == dataSourceType.BindingType || mainBindingDef.BindingType.IsAssignableFrom(dataSourceType.BindingType);
            else
                checkTypesOk = mainBindingDef.BindingType == dataSourceType.BindingType || mainBindingDef.BindingType.IsAssignableFrom(dataSourceType.BindingType);

            if (! checkTypesOk)
            {
                Type expected = mainBindingDef.BindingType;
                Type got = dataSourceType.BindingType;
                throw new BindingTemplateException(string.Format("DataSource has not got the right type. '{0}' (or a type derivated from) was expected, got '{1}'", expected.Name, got.Name));
            }
        }
        #endregion

        #region #region public methods
        public void Dispose()
        {
            if (Elements != null)
            {
                foreach (IBindingContextElement element in Elements)
                    element.Dispose();
                Elements = null;
            }
            if (ElementsToRender != null)
            {
                ElementsToRender.Clear();
                ElementsToRender = null;
            }
            if(Header != null)
            {
                Header.Dispose();
                Header = null;
            }
            if (Footer != null)
            {
                Footer.Dispose();
                Footer = null;
            }
        }
        #endregion
    }
}
